package com.eugenes.concurrency.util;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class SynchronizedQueueViaLocks<T> {

    private final T[] data;
    private final int capacity;

    private int head;
    private int tail;

    // volatile for correct get method
    private volatile int size;

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition pushCondition = lock.newCondition();
    private final Condition getCondition = lock.newCondition();

    // Casting just created object array to T type is safe.
    @SuppressWarnings("unchecked")
    public SynchronizedQueueViaLocks(int capacity) {
        if (capacity <= 0)
            throw new IllegalArgumentException("Capacity should be greater than zero.");

        data = (T[]) new Object[capacity];
        this.head = this.tail = this.size = 0;
        this.capacity = capacity;
    }

    public int getCapacity() {
        return capacity;
    }

    public int getSize() {
        return size;
    }

    private boolean privatePush(T element) {
        if (capacity == size)
            return false;

        data[tail++] = element;
        if (tail == capacity)
            tail = 0;
        ++size;
        return true;
    }

    public void push(T element) throws InterruptedException {
        if (element == null)
            throw new NullPointerException("Element cannot be null");

        lock.lock();
        try{
            while (!privatePush(element))
                getCondition.await();
            pushCondition.signalAll();
        } finally {
            lock.unlock();
        }
    }

    private T privateGet() {
        if (size == 0)
            return null;

        T result = data[head++];
        if (head == data.length)
            head = 0;
        --size;
        return result;
    }

    public T get() throws InterruptedException {
        T result;

        lock.lock();
        try {
            while ((result = privateGet()) == null)
                pushCondition.await();
            getCondition.signalAll();
        } finally {
            lock.unlock();
        }
        return result;
    }
}
